﻿using DirectShowLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace PlayCap.Wpf
{

    public class CameraCapturer
    {

        /// <summary> G/S:容器句柄 </summary>
        public IntPtr ContainerHandle { get; set; }
        /// <summary> G/S:视频尺寸 </summary>
        public Size VideoSize { get; set; }

        #region 


        // Application-defined message to notify app of filtergraph events
        public const int WM_GRAPHNOTIFY = 0x8000 + 1;

        IVideoWindow videoWindow = null;
        IMediaControl mediaControl = null;
        IMediaEventEx mediaEventEx = null;
        IGraphBuilder graphBuilder = null;
        ICaptureGraphBuilder2 captureGraphBuilder = null;

        public IBaseFilter SourceFilter { get; private set; }

        DsROTEntry rot = null;



        public void CaptureVideo()
        {
            int hr = 0;

            //try
            //{
            // Get DirectShow interfaces
            GetInterfaces();

            // Attach the filter graph to the capture graph
            hr = this.captureGraphBuilder.SetFiltergraph(this.graphBuilder);
            DsError.ThrowExceptionForHR(hr);

            // Use the system device enumerator and class enumerator to find
            // a video capture/preview device, such as a desktop USB video camera.
            IBaseFilter _sourceFilter = this.FindCaptureDevice();
            this.SourceFilter = _sourceFilter;

            // Add Capture filter to our graph.
            hr = this.graphBuilder.AddFilter(_sourceFilter, "Video Capture");
            DsError.ThrowExceptionForHR(hr);

            // Render the preview pin on the video capture filter
            // Use this instead of this.graphBuilder.RenderFile
            hr = this.captureGraphBuilder.RenderStream(PinCategory.Preview, MediaType.Video, _sourceFilter, null, null);
            DsError.ThrowExceptionForHR(hr);

            // Now that the filter has been added to the graph and we have
            // rendered its stream, we can release this reference to the filter.
            Marshal.ReleaseComObject(_sourceFilter);

            // Set video window style and position
            SetupVideoWindow();

            // Add our graph to the running object table, which will allow
            // the GraphEdit application to "spy" on our graph
            rot = new DsROTEntry(this.graphBuilder);

            // Start previewing video data
            hr = this.mediaControl.Run();
            DsError.ThrowExceptionForHR(hr);

            //}
            //catch
            //{
            //    MessageBox.Show("An unrecoverable error has occurred.");
            //}
        }

        // This version of FindCaptureDevice is provide for education only.
        // A second version using the DsDevice helper class is define later.
        public IBaseFilter FindCaptureDevice()
        {
            int hr = 0;
#if USING_NET11
      UCOMIEnumMoniker classEnum = null;
      UCOMIMoniker[] moniker = new UCOMIMoniker[1];
#else
            IEnumMoniker classEnum = null;
            IMoniker[] moniker = new IMoniker[1];
#endif
            object source = null;

            // Create the system device enumerator
            ICreateDevEnum devEnum = (ICreateDevEnum)new CreateDevEnum();

            // Create an enumerator for the video capture devices
            hr = devEnum.CreateClassEnumerator(FilterCategory.VideoInputDevice, out classEnum, 0);
            DsError.ThrowExceptionForHR(hr);

            // The device enumerator is no more needed
            Marshal.ReleaseComObject(devEnum);

            // If there are no enumerators for the requested type, then 
            // CreateClassEnumerator will succeed, but classEnum will be NULL.
            if (classEnum == null)
            {
                throw new ApplicationException("No video capture device was detected.\r\n\r\n" +
                                               "This sample requires a video capture device, such as a USB WebCam,\r\n" +
                                               "to be installed and working properly.  The sample will now close.");
            }

            // Use the first video capture device on the device list.
            // Note that if the Next() call succeeds but there are no monikers,
            // it will return 1 (S_FALSE) (which is not a failure).  Therefore, we
            // check that the return code is 0 (S_OK).
#if USING_NET11
      int i;
      if (classEnum.Next (moniker.Length, moniker, IntPtr.Zero) == 0)
#else
            if (classEnum.Next(moniker.Length, moniker, IntPtr.Zero) == 0)
#endif
            {
                // Bind Moniker to a filter object
                Guid iid = typeof(IBaseFilter).GUID;
                moniker[0].BindToObject(null, null, ref iid, out source);
            }
            else
            {
                throw new ApplicationException("Unable to access video capture device!");
            }

            // Release COM objects
            Marshal.ReleaseComObject(moniker[0]);
            Marshal.ReleaseComObject(classEnum);

            // An exception is thrown if cast fail
            return (IBaseFilter)source;
        }
        /*
            // Uncomment this version of FindCaptureDevice to use the DsDevice helper class
            // (and comment the first version of course)
            public IBaseFilter FindCaptureDevice()
            {
              System.Collections.ArrayList devices;
              object source;

              // Get all video input devices
              devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);

              // Take the first device
              DsDevice device = (DsDevice)devices[0];

              // Bind Moniker to a filter object
              Guid iid = typeof(IBaseFilter).GUID;
              device.Mon.BindToObject(null, null, ref iid, out source);

              // An exception is thrown if cast fail
              return (IBaseFilter) source;
            }
        */
        public void GetInterfaces()
        {
            int hr = 0;

            // An exception is thrown if cast fail
            this.graphBuilder = (IGraphBuilder)new FilterGraph();
            this.captureGraphBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
            this.mediaControl = (IMediaControl)this.graphBuilder;
            this.videoWindow = (IVideoWindow)this.graphBuilder;
            this.mediaEventEx = (IMediaEventEx)this.graphBuilder;

            IntPtr _handle = this.ContainerHandle;
            hr = this.mediaEventEx.SetNotifyWindow(_handle, WM_GRAPHNOTIFY, IntPtr.Zero);
            DsError.ThrowExceptionForHR(hr);
        }

        /// <summary>
        /// Void:关闭相关接口
        /// </summary>
        public void CloseInterfaces()
        {
            // Stop previewing data
            if (this.mediaControl != null)
            { this.mediaControl.StopWhenReady(); }

            // Stop receiving events
            if (this.mediaEventEx != null)
            { this.mediaEventEx.SetNotifyWindow(IntPtr.Zero, WM_GRAPHNOTIFY, IntPtr.Zero); }

            // Relinquish ownership (IMPORTANT!) of the video window.
            // Failing to call put_Owner can lead to assert failures within
            // the video renderer, as it still assumes that it has a valid
            // parent window.
            if (this.videoWindow != null)
            {
                this.videoWindow.put_Visible(OABool.False);
                this.videoWindow.put_Owner(IntPtr.Zero);
            }

            // Remove filter graph from the running object table
            if (rot != null)
            {
                rot.Dispose();
                rot = null;
            }

            // Release DirectShow interfaces
            Marshal.ReleaseComObject(this.mediaControl); this.mediaControl = null;
            Marshal.ReleaseComObject(this.mediaEventEx); this.mediaEventEx = null;
            Marshal.ReleaseComObject(this.videoWindow); this.videoWindow = null;
            Marshal.ReleaseComObject(this.graphBuilder); this.graphBuilder = null;
            Marshal.ReleaseComObject(this.captureGraphBuilder); this.captureGraphBuilder = null;
        }

        public void SetupVideoWindow()
        {
            int hr = 0;

            IntPtr _handle = this.ContainerHandle;

            // Set the video window to be a child of the main window
            hr = this.videoWindow.put_Owner(_handle);
            DsError.ThrowExceptionForHR(hr);

            hr = this.videoWindow.put_WindowStyle(DirectShowLib.WindowStyle.Child | DirectShowLib.WindowStyle.ClipChildren);
            DsError.ThrowExceptionForHR(hr);

            // Use helper function to position video window in client rect 
            // of main application window
            ResizeVideoWindow();

            // Make the video window visible, now that it is properly positioned
            hr = this.videoWindow.put_Visible(OABool.True);
            DsError.ThrowExceptionForHR(hr);
        }

        public void ResizeVideoWindow()
        {
            // Resize the video preview window to match owner window size
            if (this.videoWindow != null)
            {
                this.videoWindow.SetWindowPosition(0, 0, (int)this.VideoSize.Width, (int)this.VideoSize.Height);
            }
        }


        //public void RotateVideoWindow()
        //{
        //    // Resize the video preview window to match owner window size
        //    if (this.videoWindow != null)
        //    {
        //        this.videoWindow.SetWindowPosition(0, 0, (int)this.VideoSize.Height, (int)this.VideoSize.Width);
        //    }
        //    var 
        //}


        /// <summary>
        /// 获取摄像头所支持的分辨率
        /// </summary>
        /// <param name="capGraph"></param>
        /// <param name="captureFilter"></param>
        public Dictionary<string, Size> GetResolutionDict()
        {
            ICaptureGraphBuilder2 _builder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
            IBaseFilter _filter = this.FindCaptureDevice();
            return DirectShowUtility.GetCaptureResolutionDict(_builder, _filter);
        }

        #endregion

    }
}
